/*
 * Cliente.cpp
 *
 *  PEREIRA, FERNANDO ROQUE
 *  91610
 */

#include "Cliente.h"
#include "Diferencia.h"

Cliente::Cliente(const char* username, const char* pass, const char* syncPath,
		const char* indexPath, unsigned int intervaloTiempoPolling) :
		indiceHashes(indexPath, std::fstream::in | std::fstream::out),
		poller(indiceHashes, syncPath, indexPath),
		syncHandler(syncPath, indiceHashes, indexPath),
		notifListener(this->colaNotif) {
	if (!indiceHashes) {
		throw MyException("No se pudo abrir el indice de hashes local\n");
	}
	this->nombreUsuario = username;
	this->password = pass;
	this->syncPath = syncPath;
	this->intervaloPolling = intervaloTiempoPolling;
}

Cliente::~Cliente() {}

void Cliente::iniciarTrabajo(int numPuerto, const char* ip) throw() {
	//	Poll inicial, no envia actualizacion al server hasta no conectarse
	std::cout << "Polling inicial..." << std::endl;
	this->poller.poll();

	if (this->comunicador.conectar(numPuerto, ip)) {
		throw MyException("El Cliente no logró conectarse al servidor\n");
	}

	std::cout << "Cliente.iniciarTrabajo) Conectado al server" << std::endl;

	// Se loggea al server con su usuario y contrasenia
	this->loginToServer();
	std::cout << "Cliente.iniciarTrabajo) Loggeado al server" << std::endl;
	std::cout << "Cliente.iniciarTrabajo) Intentando conectar al puerto auxiliar: "
			<< this->comunicador.getSockPort() + 1 << std::endl;

	sleep(3);
	// Integro el HMAC con el pass del user
	this->comunicador.setClaveSimetrica(this->password);

	this->notifListener.conectarAlServer(ip, this->comunicador.getSockPort() + 1);

	/* Sincronizo solo archivos nuevos y borrados offline (los archivos
	 * modificados se sincronizan en el getRepoServer()
	 */
	this->manejarDiferencias(this->getListaDiferenciasIniciales());

	// Sincronizo con el server
	this->getRepoServer();

	std::cout << "Cliente.iniciarTrabajo) Sincronizacion inicial con server terminada"
			<< std::endl;
}

std::list<Diferencia*> Cliente::getListaDiferenciasIniciales() {
	std::list<Diferencia*> difIniciales;
	std::list<std::string> newFiles = this->poller.getListaArchivosNuevos();
	std::list<std::string> deletedFiles = this->poller.getListaArchivosBorrados();

	// Armo diferencias de archivos nuevos y borrados offline
	for (std::list<std::string>::iterator it = newFiles.begin();
			it != newFiles.end(); it++) {
		difIniciales.push_back(new DiferenciaNewFile(it->c_str(), this->syncPath));
	}

	for (std::list<std::string>::iterator it = deletedFiles.begin();
			it != deletedFiles.end(); it++) {
		difIniciales.push_back(new DiferenciaDeletedFile(it->c_str()));
	}

	return (difIniciales);
}

//	Envio username y pass al server para validar la cuenta. POST: Loggea o
//	caso contrario
//	envia exception si username o pass esta mal
void Cliente::loginToServer() {
	CommandMaker c;
	std::string mensajeRecibido;
	std::string mensajeLogin = c.getLoginMessage(this->nombreUsuario, this->password);
	this->comunicador.enviarMensaje(mensajeLogin);
	this->comunicador.recibirMensaje(mensajeRecibido);
	if (mensajeRecibido.compare(LOGINFAIL) == 0) {
		throw MyException("Nombre usuario o contrasenia incorrectos. "
				"Muere cliente muereeee!\n");
	} else {
		std::cout << "Loggeado al server correctamente." << std::endl;
	}
}

void Cliente::run() {
	/*
	 1) Pedirle al server los archivos
	 2) Corroborar actualizaciones
	 3) Si corresponde, pedir data de archivos
	 4) Si corresponde, enviar data de archivos para que el server actualice
	*/
	notifListener.start();
	sleep(this->intervaloPolling);
	while (running) {
		// Manejo de notificaciones entrante
		while (!this->colaNotif.vacia()) {
			// Manejo cada notificacion
			std::string mensaje = this->colaNotif.pop();
			std::stringstream stream(mensaje);
			this->manejarActualizaciones(stream);
		}
		this->poller.poll();
		this->manejarDiferencias(this->poller.getListaDiferencias());

		sleep(this->intervaloPolling);
	}
	this->terminarTrabajo();
}

void Cliente::manejarDiferencias(std::list<Diferencia*> diferencias) {
	if (diferencias.size() == 0) {
		std::cout << "Cliente.manejarDiferencias) No hay diferencias!" << std::endl;
		return;
	}
	//	Por cada diferencia que hay en la lista, me comunico con el server
	for (std::list<Diferencia*>::iterator it=diferencias.begin();
			it != diferencias.end();
			++it) {
		std::stringstream stream((*it)->getComandoDiferencia());
		this->manejarActualizaciones(stream);
	}
}

void Cliente::manejarActualizaciones(std::stringstream& stream) {
	ClientCommandInterpreter interprete;
	std::string comando, aux;
	stream >> comando;
	std::cout << "Cliente.manejarActualizaciones) Diferencia a enviar:  " << comando
			<< std::endl;
	aux = stream.str();
	std::stringstream streamAux(aux.substr(aux.find_first_of(' ') + 1));
	interprete[comando]->parametros(streamAux.str());
	interprete[comando]->execute(this->syncHandler, this->comunicador);
}

/* El cliente, apenas loggeado, lo primero que hace es pedirle la lista de
 * archivos y sus hashes correspondientes al repo, para actualizar el repo local
 * y actuar en consecuencia
 */
void Cliente::getRepoServer() {
	CommandMaker commandMaker;
	std::string mensajeRecibido;
	FileInfo fInfo;
	std::list<FileInfo> listaFiles;
	std::string mensaje = commandMaker.getFileList();
	this->comunicador.enviarMensaje(mensaje);
	this->comunicador.recibirMensaje(mensajeRecibido);

	std::list<std::string> newFiles = this->poller.getListaArchivosNuevos();
	std::list<std::string> deletedFiles = this->poller.getListaArchivosBorrados();

	// Parseo la respuesta del server FILE-LIST <filename> <hash> <date>....
	std::stringstream stream(mensajeRecibido.substr(mensajeRecibido.find_first_of(' ',
			0) + 1, mensajeRecibido.size()));

	if ((stream.str().compare("") == 0) && (newFiles.size() == 0)) {
		// Server vacio, borro repo local
		this->syncHandler.borrarTodo();
		return;
	}

	/*Leo toda la lista de archivos, sumo a la lista y la paso al FileSync
	 * para actualizar
	 */
	stream >> fInfo;
	while (fInfo.getFilename().compare("") != 0) {
		listaFiles.push_back(fInfo);
		stream >> fInfo;
	}

	// Sincronizo repo local con repo del server
	this->syncHandler.mergearRepos(listaFiles, this->poller.getListaOldHashes());
	std::list<Diferencia*> dif = this->syncHandler.getListaDiferencias();
	std::list<Diferencia*>::iterator it = dif.begin();
	while (it != dif.end()) {
		std::stringstream stream((*it)->getComandoDiferencia());
		this->manejarActualizaciones(stream);
		it++;
	}
}

FileSyncHandler& Cliente::getSyncHandler() {
	return (this->syncHandler);
}

void Cliente::terminarTrabajo() {
	this->notifListener.stop();
	this->notifListener.terminarTrabajo();
	this->notifListener.join();
	this->indiceHashes.close();
	this->comunicador.cerrarComunicacion();
}
